﻿/*
* Copyright (C) 2020 ~ 2024 chaigec All Rights Reserved.
*
* Author:     chaigec <chaigec@163.com>
* Maintainer: chaigec <chaigec@163.com>

 * Use of this source code is governed by MIT license that can be found in the
 * LICENSE file in the root of the source tree. All contributing project authors
 * may be found in the AUTHORS file in the root of the source tree.
*/

#include "MkHttpApi.h"
#include "MkHttp/MkHttpRequest.h"
#include "MkHttp/MkHttpUri.h"
#include "MkHttp/MkHttpSession.h"
#include "MkEvent/MkEvent.h"
#include "MkUtil/MkUtil.h"
#include "MkUtil/MkErrorCode.h"
#include "MkUtil/MkLog.h"
#include "MkUtil/MkBase64.h"
#include "MkUtil/MkFile.h"

static const MkString EmptyMkString = MkString("");
CMkHttpApi::CMkHttpApi(CMkEventPool* pEventPool, CMkTimer* pTimer)
    : CMkHttpServer(pEventPool, pTimer)
    , m_RequestQueue(1000)
    , m_TaskRun(FALSE)
    , m_TaskThreadCount(4)
    , m_WebSocketPath("/api/v1/ws")
    , m_ApiUriPrefix("/api/v1/")
    , m_UserName("")
    , m_Password("")
    , m_SerialQueue(1000)
    , m_HasSerialTask(FALSE)
    , m_MaxWsConnectorSize(3)
{
    CMkUtil::CreateRandomString(Len16, m_WebSocketAccessKey);
}

Uint32 CMkHttpApi::StartServer(Uint16 Port, const MkString& Host/*=""*/, Uint32 TaskCount/* = 10*/)
{
    Uint32 ErrorCode = NoneError;
    if (0 != TaskCount) {
        m_TaskThreadCount = TaskCount > 100 ? 100 : TaskCount;
    }
    if (m_TaskRun) {
        for (Uint32 Index = 0; Index < m_TaskThreadCount; Index++) {
            CMkThread *pThread = new CMkThread;
            if (!pThread) {
                continue;
            }
            ErrorCode = pThread->Start([this]() {DoTaskThread(); });
            if (NoneError != ErrorCode) {
                delete pThread;
                continue;
            }
            m_VecTaskThread.push_back(pThread);
        }
    }
    if (m_HasSerialTask) {
        ErrorCode = m_SerialThread.Start([this]() {DoSerialTaskThread(); });
        MkCheckErrorLog(ErrorCode, "start serial task thread failed\n");
    }
    ErrorCode = CMkHttpServer::StartServer(Port, Host);
    MkCheckErrorLog(ErrorCode, "start http server port:%d failed\n", Port);
    MkInfoLog("start http server port:%d success\n", Port);
    return NoneError;
}

Uint32 CMkHttpApi::BroadCastMsg(const MkString& Msg)
{
    m_WsClientLock.Lock();
    MkVector<CMkHttpSession*>::iterator it = m_VecWsSession.begin();
    for (; it != m_VecWsSession.end(); it++) {
        (*it)->SendWebSocketText(Msg);
    }
    m_WsClientLock.Unlock();
    return NoneError;
}

Uint32 CMkHttpApi::AddTaskPath(const MkHttpMethod& Method, const MkString& Path, BOOL bSerial/*=FALSE*/)
{
    Uint32 ErrorCode = NoneError;
    MkString RealPath = GetApiUriPrefix() + Path;
    switch (Method) {
    case MkHttpMethodGet:
        m_MapGetTaskPath[RealPath] = bSerial;
        break;
    case MkHttpMethodPost:
        m_MapPostTaskPath[RealPath] = bSerial;
        break;
    case MkHttpMethodPut:
        m_MapPutTaskPath[RealPath] = bSerial;
        break;
    case MkHttpMethodDelete:
        m_MapDeleteTaskPath[RealPath] = bSerial;
        break;
    default:
        ErrorCode = HttpResponseErrorBegin + MkHttpStateMethodNotAllowed;
        break;
    }
    m_TaskRun = TRUE;
    if (bSerial) {
        m_HasSerialTask = TRUE;
    }
    return ErrorCode;
}

void CMkHttpApi::SetWebSocketAccessKey(const MkString& Key)
{
    if (!Key.empty()) {
        m_WebSocketAccessKey = Key;
    }
}

void CMkHttpApi::SetMaxWsConnectorSize(const Uint32& MaxSize)
{
    if (0 < MaxSize
        && MaxSize <= 5) {
        m_MaxWsConnectorSize = MaxSize;
    }
}

void CMkHttpApi::SetUserPass(const MkString &UserName, const MkString &Password)
{
    m_UserName = UserName;
    m_Password = Password;
    if (!m_UserName.empty() && !m_Password.empty()) {
        MkBase64Encode(UserName + ":" + Password, m_BaseAuthor);
        m_BaseAuthor = "Basic " + m_BaseAuthor;
    } else {
        m_BaseAuthor.clear();
    }
}

Uint32 CMkHttpApi::StopServer()
{
    m_TaskRun = FALSE;
    MkHttpRequestParam Param;
    Param.ResCb = nullptr;
    for (size_t i = 0; i < m_VecTaskThread.size(); i++) {
        m_RequestQueue.Input(Param);
    }
    m_SerialQueue.Input(Param);

    MkVector<CMkThread*>::iterator itTask = m_VecTaskThread.begin();
    for (; itTask != m_VecTaskThread.end(); itTask++) {
        (*itTask)->Stop();
        MkDelete(*itTask);
    }
    m_SerialThread.Stop();
    m_VecTaskThread.clear();
    m_RequestQueue.Clear();
    m_SerialQueue.Clear();
    return CMkHttpServer::StopServer();
}

Uint32 CMkHttpApi::OnRequestJsonParse(const MkHttpMethod& Method, const MkString& Path, const MkMapString& MapQuery, const Json::Value& JsonReq, const MkHttpResponseJsonCb& cb)
{
    cb(NoneError, Json::Value());
    return NoneError;
}

Uint32 CMkHttpApi::OnWebSockekAccessed(CMkHttpSession* pSession)
{
    return NoneError;
}

Uint32 CMkHttpApi::OnRecvRequest(CMkHttpSession* pSession, const CMkHttpRequest& Request)
{
    Uint32 ErrorCode = NoneError;
    const MkString Path = Request.GetUri().GetUriPath();
    MkHttpMethod Method = Request.GetMethod();
    MkHttpStatusCode StatusCode = MkHttpStateUnauthorized;
    if (Path == m_WebSocketPath) {
        ErrorCode = pSession->SendWebsocketConnectResponse(Request);
        pSession->SetSendPingFlag(TRUE);
        return OnWebsocketConnected(pSession);
    }

    BOOL bCheckAuthor = TRUE;
    if (!m_WhitePath.empty()
        && 0 == Path.find(m_WhitePath)) {
        bCheckAuthor = FALSE;
    }
    if (bCheckAuthor) {
        MkString strAuthor;
        ErrorCode = Request.GetHeadValue("Authorization", strAuthor);
        if (!m_BaseAuthor.empty() && m_BaseAuthor != strAuthor) {
            MkErrorLog("author failed\n");
            ErrorCode = pSession->SendReply(MkHttpStateUnauthorized);
            return ErrorCode;
        }
    }

    Uint64 HttpSessionId = pSession->GetSessionId();

    ErrorCode = HttpRequestParse(Request, [HttpSessionId, this](const MkHttpBodyType& BodyType, const MkString& Response) {
        SendResponse(HttpSessionId, MkHttpStateOk, BodyType, Response);
    });
    if (NoneError != ErrorCode) {
        pSession->SendReply(MkHttpStateNotFound, MkHttpBodyTypeHtml, NotFoundString);
    }
    return ErrorCode;
}

Uint32 CMkHttpApi::OnWebsocketRecved(CMkHttpSession* pSession, const MkString& Message)
{
    Uint32 ErrorCode = NoneError;
    if (Message == m_WebSocketAccessKey) {
        if (m_MaxWsConnectorSize <= m_VecWsSession.size()) {
            pSession->SendWebSocketText("ws connection is full\n");
            ErrorCode = SystemError;
        } else {
            m_WsClientLock.Lock();
            m_VecWsSession.push_back(pSession);
            m_WsClientLock.Unlock();
            OnWebSockekAccessed(pSession);
        }
    } else {
        pSession->SendWebSocketText("recv error access key\n");
        ErrorCode = SystemError;
    }
    return ErrorCode;
}

Uint32 CMkHttpApi::OnWebsocketClosed(CMkHttpSession* pSession)
{
    m_WsClientLock.Lock();
    MkVector<CMkHttpSession*>::iterator it = m_VecWsSession.begin();
    while (it != m_VecWsSession.end()) {
        if (*it == pSession) {
            it = m_VecWsSession.erase(it);
        } else {
            it++;
        }
    }
    m_WsClientLock.Unlock();
    return NoneError;
}

Uint32 CMkHttpApi::OnWebsocketConnected(CMkHttpSession* pSession)
{
    return NoneError;
}

Uint32 CMkHttpApi::DoTaskThread()
{
    MkHttpRequestParam Param;
    while (m_TaskRun) {
        m_RequestQueue.Output(Param, 0xFFFFFFFF);
        if (nullptr != Param.ResCb) {
            DoHttpRequestParse(Param.Request, Param.ResCb);
            Param.Request.Release();
            Param.ResCb = nullptr;
        }
    }
    return NoneError;
}

Uint32 CMkHttpApi::DoSerialTaskThread()
{
    MkHttpRequestParam Param;
    while (m_TaskRun) {
        m_SerialQueue.Output(Param, 0xFFFFFFFF);
        if (nullptr != Param.ResCb) {
            DoHttpRequestParse(Param.Request, Param.ResCb);
            Param.Request.Release();
            Param.ResCb = nullptr;
        }
    }
    return NoneError;
}

Uint32 CMkHttpApi::HttpRequestParse(const CMkHttpRequest& Request, const HttpResponseCb& cb)
{
    Uint32 ErrorCode = NoneError;
    BOOL bTask = FALSE;
    BOOL bSerial = FALSE;
    ErrorCode = GetPathParam(Request.GetMethod(), Request.GetUri().GetUriPath(), bTask, bSerial);
    if (bTask) {
        MkHttpRequestParam Param;
        Param.Request = Request;
        Param.ResCb = cb;
        if (bSerial) {
            m_SerialQueue.Input(Param);
        } else {
            m_RequestQueue.Input(Param);
        }
        ErrorCode = NoneError;
    } else {
        ErrorCode = DoHttpRequestParse(Request, cb);
    }
    return NoneError;
}

Uint32 CMkHttpApi::DoHttpRequestParse(const CMkHttpRequest& Request, const HttpResponseCb& cb)
{
    MkString Path = Request.GetUri().GetUriPath();
    Uint32 ErrorCode = NoneError;
    MkHttpStatusCode StatusCode = MkHttpStateOk;

    if (0 != Path.find(m_ApiUriPrefix)
        || Path.size() == m_ApiUriPrefix.size()) {
        ErrorCode = SendResponse(Request.GetSessionId(), MkHttpStateNotFound, MkHttpBodyTypeHtml, NotFoundString);
        return ErrorCode;
    }

    Path = Path.substr(m_ApiUriPrefix.size());
    MkString strRequest = Request.GetBody();
    Json::Value JsonRequest;
    try {
        Json::CharReaderBuilder b;
        Json::CharReader *reader(b.newCharReader());
        if (!reader) {
            ErrorCode = MallocFailed;
        } else {
            MkString errs;
            reader->parse(strRequest.c_str(), strRequest.c_str() + strRequest.size(), &JsonRequest, &errs);
            delete reader;
            ErrorCode = OnRequestJsonParse(Request.GetMethod(), Path, Request.GetUri().GetParams(), JsonRequest, [this, cb](Uint32 ErrorCode, const Json::Value &ResBody) {
                ResponseMessage(ErrorCode, ResBody, cb);
            });
        }
    } catch (Json::Exception &e) {
        MkErrorLog("body:%s\n", strRequest.c_str());
        MkErrorLog("json parse error:%s\n", e.what());
        ErrorCode = InvalidParams;
    }
    if (NoneError != ErrorCode) {
        ResponseMessage(ErrorCode, Json::Value(), cb);
    }
    return NoneError;
}

Uint32 CMkHttpApi::ResponseMessage(Uint32 ErrorCode, const Json::Value &ResBody, const HttpResponseCb& cb)
{
    Json::Value value;
    MkString strResponse;
    if (NoneError == ErrorCode && !ResBody.empty()) {
        value["data"] = ResBody;
    }
    if (InvalidUrl == ErrorCode) {
        value["support"] = ResBody;
    }
    value["code"] = ErrorCode;
    if (NoneError != ErrorCode) {
        CMkErrorCode::ErrorCodeDesc Desc;
        CMkErrorCode::GetInstance().GetErrorcodeDesc(ErrorCode, Desc);
        value["cnDescribe"] = Desc.CnDesc;
        value["enDescribe"] = Desc.EnDesc;

        value["cnTreatmentMethod"] = Desc.CnTreatmentMethod;
        value["enTreatmentMethod"] = Desc.EnTreatmentMethod;
    }
    strResponse = value.toStyledString();
    cb(MkHttpBodyTypeJson, strResponse);
    return NoneError;
}

MkHttpStatusCode CMkHttpApi::UploadRequest(const CMkHttpRequest& Request, const MkString& FilePath)
{
    if (CMkFile::CheckFileIsExist(FilePath)) {
        return MkHttpStateFound;
    }
    FILE *fp = fopen(FilePath.c_str(), "wb");
    if (!fp) {
        return MkHttpStateNotFound;
    }
    fwrite(Request.GetBody().c_str(), 1, Request.GetBody().size(), fp);
    fclose(fp);
    return MkHttpStateOk;
}

MkHttpStatusCode CMkHttpApi::DeleteRequest(const CMkHttpRequest& Request, const MkString& FilePath)
{
    if (!CMkFile::CheckFileIsExist(FilePath)) {
        return MkHttpStateNotFound;
    }
    CMkFile::DeleteLocalFile(FilePath);
    return MkHttpStateOk;
}

MkHttpStatusCode CMkHttpApi::UpdateRequest(const CMkHttpRequest& Request, const MkString& FilePath)
{
    if (!CMkFile::CheckFileIsExist(FilePath)) {
        return MkHttpStateNotFound;
    }
    FILE *fp = fopen(FilePath.c_str(), "wb");
    if (!fp) {
        return MkHttpStateNotFound;
    }
    fwrite(Request.GetBody().c_str(), 1, Request.GetBody().size(), fp);
    fclose(fp);
    return MkHttpStateOk;
}

Uint32 CMkHttpApi::GetPathParam(const MkHttpMethod& Method, const MkString& Path, BOOL &bTask, BOOL &bSerial)
{
    Uint32 ErrorCode = NoneError;
    switch (Method) {
    case MkHttpMethodGet:
        ErrorCode = CheckPathInMap(Path, m_MapGetTaskPath, bSerial);
        break;
    case MkHttpMethodPost:
        ErrorCode = CheckPathInMap(Path, m_MapPostTaskPath, bSerial);
        break;
    case MkHttpMethodPut:
        ErrorCode = CheckPathInMap(Path, m_MapPutTaskPath, bSerial);
        break;
    case MkHttpMethodDelete:
        ErrorCode = CheckPathInMap(Path, m_MapDeleteTaskPath, bSerial);
        break;
    default:
        break;
    }
    bTask = (NoneError == ErrorCode);
    return NoneError;
}

Uint32 CMkHttpApi::CheckPathInMap(const MkString& Path, const MkMap<MkString, Uint32>& MapPath, BOOL &bSerial)
{
    MkMap<MkString, Uint32>::const_iterator it = MapPath.find(Path);
    if (it != MapPath.end()) {
        bSerial = it->second;
        return NoneError;
    }
    return InvalidParams;
}

CMkHttpApi::~CMkHttpApi()
{
    //StopServer();
}